{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Use DICOMweb&trade; Standard APIs with Python"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This tutorial uses Python to demonstrate working with the Medical Imaging Server for DICOM.\n",
    "\n",
    "For the tutorial we will use the DICOM files here: [Sample DICOM files](../dcms). The file name, studyUID, seriesUID and instanceUID of the sample DICOM files is as follows:\n",
    "\n",
    "| File | StudyUID | SeriesUID | InstanceUID |\n",
    "| --- | --- | --- | ---|\n",
    "|green-square.dcm|1.2.826.0.1.3680043.8.498.13230779778012324449356534479549187420|1.2.826.0.1.3680043.8.498.45787841905473114233124723359129632652|1.2.826.0.1.3680043.8.498.12714725698140337137334606354172323212|\n",
    "|red-triangle.dcm|1.2.826.0.1.3680043.8.498.13230779778012324449356534479549187420|1.2.826.0.1.3680043.8.498.45787841905473114233124723359129632652|1.2.826.0.1.3680043.8.498.47359123102728459884412887463296905395|\n",
    "|blue-circle.dcm|1.2.826.0.1.3680043.8.498.13230779778012324449356534479549187420|1.2.826.0.1.3680043.8.498.77033797676425927098669402985243398207|1.2.826.0.1.3680043.8.498.13273713909719068980354078852867170114|\n",
    "\n",
    "> NOTE: Each of these files represent a single instance and are part of the same study. Also green-square and red-triangle are part of the same series, while blue-circle is in a separate series.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Prerequisites\n",
    "\n",
    "In order to use the DICOMWeb&trade; Standard APIs, you must have an instance of the Medical Imaging Server for DICOM deployed. If you have not already deployed the Medical Imaging Server, [Deploy the Medical Imaging Server to Azure](../quickstarts/deploy-via-azure.md).\n",
    "\n",
    "Once you have deployed an instance of the Medical Imaging Server for DICOM, retrieve the URL for your App Service:\n",
    "\n",
    "1. Sign into the [Azure Portal](https://portal.azure.com/).\n",
    "1. Search for **App Services** and select your Medical Imaging Server for DICOM App Service.\n",
    "1. Copy the **URL** of your App Service.\n",
    "\n",
    "For this code, we'll be accessing an unsecured dev/test service. Please don't upload any private health information (PHI).\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Working with the Medical Imaging Server for DICOM \n",
    "The DICOMweb&trade; standard makes heavy use of `multipart/related` HTTP requests combined with DICOM specific accept headers. Developers familiar with other REST-based APIs often find working with the DICOMweb&trade; standard awkward. However, once you have it up and running, it's easy to use. It just takes a little finagling to get started."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Import the appropriate Python libraries\n",
    "\n",
    "First, import the necessary Python libraries. \n",
    "\n",
    "We've chosen to implement this example using the synchronous `requests` library. For asnychronous support, consider using `httpx` or another async library. Additionally, we're importing two supporting functions from `urllib3` to support working with `multipart/related` requests."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests\n",
    "import pydicom\n",
    "from pathlib import Path\n",
    "from urllib3.filepost import encode_multipart_formdata, choose_boundary"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Configure user-defined variables to be used throughout\n",
    "Replace all variable values wrapped in { } with your own values. Additionally, validate that any constructed variables are correct.  For instance, `base_url` is constructed using the default URL for Azure App Service. If you're using a custom URL, you'll need to override that value with your own."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dicom_server_name = \"{server-name}\"\n",
    "path_to_dicoms_dir = \"{path to the folder that includes green-square.dcm and other dcm files}\"\n",
    "\n",
    "base_url = f\"https://{dicom_server_name}.azurewebsites.net\"\n",
    "base_url"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dicom_server_name = \"sjbdicomtest\"\n",
    "path_to_dicoms_dir = \"c:\\\\githealth\\\\dicom-server\\\\docs\\\\dcms\\\\\"\n",
    "\n",
    "base_url = f\"https://{dicom_server_name}.azurewebsites.net\"\n",
    "base_url"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "study_uid = \"1.2.826.0.1.3680043.8.498.13230779778012324449356534479549187420\"; #StudyInstanceUID for all 3 examples\n",
    "series_uid = \"1.2.826.0.1.3680043.8.498.45787841905473114233124723359129632652\"; #SeriesInstanceUID for green-square and red-triangle\n",
    "instance_uid = \"1.2.826.0.1.3680043.8.498.47359123102728459884412887463296905395\"; #SOPInstanceUID for red-triangle"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create supporting methods to support `multipart\\related`\n",
    "The `Requests` library (and most Python libraries) do not work with `multipart\\related` in a way that supports DICOMweb&trade;. Because of this, we need to add a few methods to support working with DICOM files.\n",
    "\n",
    "`encode_multipart_related` takes a set of fields (in the DICOM case, these are generally Part 10 dcm files) and an optional user defined boundary. It returns both the full body, along with the content_type, which can be used \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def encode_multipart_related(fields, boundary=None):\n",
    "    if boundary is None:\n",
    "        boundary = choose_boundary()\n",
    "\n",
    "    body, _ = encode_multipart_formdata(fields, boundary)\n",
    "    content_type = str('multipart/related; boundary=%s' % boundary)\n",
    "\n",
    "    return body, content_type"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create a `requests` session\n",
    "Create a `requests` session, called `client`, that will be used to communicate with the Medical Imaging Server for DICOM."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "client = requests.session()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Store DICOM Instances (STOW)\n",
    "\n",
    "The following examples highlight persisting DICOM files."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Store-instances-using-multipart/related\n",
    "\n",
    "This demonstrates how to upload a single DICOM file. This uses a bit of a Python hack to pre-load the DICOM file (as bytes) into memory.  By passing an array of files to the fields parameter ofencode_multipart_related, multiple files can be uploaded in a single POST. This is sometimes used to upload a complete Series or Study.\n",
    "\n",
    "_Details:_\n",
    "\n",
    "* Path: ../studies\n",
    "* Method: POST\n",
    "* Headers:\n",
    "    * `Accept: application/dicom+json`\n",
    "    * `Content-Type: multipart/related; type=\"application/dicom\"`\n",
    "* Body:\n",
    "    * `Content-Type: application/dicom` for each file uploaded, separated by a boundary value\n",
    "\n",
    "> Some programming languages and tools behave differently. For instance, some require you to define your own boundary. For those, you may need to use a slightly modified Content-Type header. The following have been used successfully.\n",
    " > * `Content-Type: multipart/related; type=\"application/dicom\"; boundary=ABCD1234`\n",
    " > * `Content-Type: multipart/related; boundary=ABCD1234`\n",
    " > * `Content-Type: multipart/related`\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#upload blue-circle.dcm\n",
    "filepath = Path(path_to_dicoms_dir).joinpath('blue-circle.dcm')\n",
    "\n",
    "# Hack. Need to open up and read through file and load bytes into memory \n",
    "with open(filepath,'rb') as reader:\n",
    "    rawfile = reader.read()\n",
    "files = {'file': ('dicomfile', rawfile, 'application/dicom')}\n",
    "\n",
    "#encode as multipart_related\n",
    "body, content_type = encode_multipart_related(fields = files)\n",
    "\n",
    "headers = {'Accept':'application/dicom+json', \"Content-Type\":content_type}\n",
    "\n",
    "url = f'{base_url}/studies'\n",
    "response = client.post(url, body, headers=headers, verify=False)\n",
    "response"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Store-instances-for-a-specific-study\n",
    "\n",
    "This demonstrates how to upload a multiple DICOM files into the specified study. This uses a bit of a Python hack to pre-load the DICOM file (as bytes) into memory.  \n",
    "\n",
    "By passing an array of files to the fields parameter of `encode_multipart_related`, multiple files can be uploaded in a single POST. This is sometimes used to upload a complete Series or Study. \n",
    "\n",
    "_Details:_\n",
    "* Path: ../studies/{study}\n",
    "* Method: POST\n",
    "* Headers:\n",
    "    * `Accept: application/dicom+json`\n",
    "    * `Content-Type: multipart/related; type=\"application/dicom\"`\n",
    "* Body:\n",
    "    * `Content-Type: application/dicom` for each file uploaded, separated by a boundary value\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "filepath_red = Path(path_to_dicoms_dir).joinpath('red-triangle.dcm')\n",
    "filepath_green = Path(path_to_dicoms_dir).joinpath('green-square.dcm')\n",
    "\n",
    "# Hack. Need to open up and read through file and load bytes into memory \n",
    "with open(filepath_red,'rb') as reader:\n",
    "    rawfile_red = reader.read()\n",
    "with open(filepath_green,'rb') as reader:\n",
    "    rawfile_green = reader.read()  \n",
    "       \n",
    "files = {'file_red': ('dicomfile', rawfile_red, 'application/dicom'),\n",
    "         'file_green': ('dicomfile', rawfile_green, 'application/dicom')}\n",
    "\n",
    "#encode as multipart_related\n",
    "body, content_type = encode_multipart_related(fields = files)\n",
    "\n",
    "headers = {'Accept':'application/dicom+json', \"Content-Type\":content_type}\n",
    "\n",
    "url = f'{base_url}/studies'\n",
    "response = client.post(url, body, headers=headers, verify=False)\n",
    "response\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Store single instance (non-standard)\n",
    "\n",
    "This demonstrates how to upload a single DICOM file. This non-standard API endpoint simplifies uploading a single file as a byte array stored in the body of a request.\n",
    "\n",
    "_Details:_\n",
    "* Path: ../studies\n",
    "* Method: POST\n",
    "* Headers:\n",
    "   *  `Accept: application/dicom+json`\n",
    "   *  `Content-Type: application/dicom`\n",
    "* Body:\n",
    "    * Contains a single DICOM file as binary bytes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#upload blue-circle.dcm\n",
    "filepath = Path(path_to_dicoms_dir).joinpath('blue-circle.dcm')\n",
    "\n",
    "# Hack. Need to open up and read through file and load bytes into memory \n",
    "with open(filepath,'rb') as reader:\n",
    "    body = reader.read()\n",
    "\n",
    "headers = {'Accept':'application/dicom+json', 'Content-Type':'application/dicom'}\n",
    "\n",
    "url = f'{base_url}/studies'\n",
    "response = client.post(url, body, headers=headers, verify=False)\n",
    "response  # response should be a 409 Conflict if the file was already uploaded abovin the above request"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Retrieve DICOM Instances (WADO)\n",
    "\n",
    "The following examples highlight retrieving DICOM instances."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Retrieve all instances within a study\n",
    "\n",
    "This retrieves all instances within a single study.\n",
    "\n",
    "_Details:_\n",
    "* Path: ../studies/{study}\n",
    "* Method: GET\n",
    "* Headers:\n",
    "   * `Accept: multipart/related; type=\"application/dicom\"; transfer-syntax=*`\n",
    "\n",
    "All three of the dcm files that we uploaded previously are part of the same study so the response should return all 3 instances. Validate that the response has a status code of OK and that all three instances are returned.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "url = f'{base_url}/studies/{study_uid}'\n",
    "headers = {'Accept':'multipart/related; type=\"application/dicom\"; transfer-syntax=*'}\n",
    "\n",
    "response = client.get(url, headers=headers) #, verify=False)\n",
    "response\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Use the retrieved instances\n",
    "The instances are retrieved as binary bytes. You can loop through the returned items and convert the bytes into a file-like structure which can be read by `pydicom`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests_toolbelt as tb\n",
    "from io import BytesIO\n",
    "\n",
    "mpd = tb.MultipartDecoder.from_response(response)\n",
    "for part in mpd.parts:\n",
    "    # Note that the headers are returned as binary!\n",
    "    print(part.headers[b'content-type'])\n",
    "    \n",
    "    # You can convert the binary body (of each part) into a pydicom DataSet\n",
    "    #   And get direct access to the various underlying fields\n",
    "    dcm = pydicom.dcmread(BytesIO(part.content))\n",
    "    print(dcm.PatientName)\n",
    "    print(dcm.SOPInstanceUID)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Retrieve metadata of all instances in study\n",
    "\n",
    "This request retrieves the metadata for all instances within a single study.\n",
    "\n",
    "_Details:_\n",
    "* Path: ../studies/{study}/metadata\n",
    "* Method: GET\n",
    "* Headers:\n",
    "   * `Accept: application/dicom+json`\n",
    "\n",
    "All three of the dcm files that we uploaded previously are part of the same study so the response should return the metadata for all 3 instances. Validate that the response has a status code of OK and that all the metadata is returned."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "url = f'{base_url}/studies/{study_uid}/metadata'\n",
    "headers = {'Accept':'application/dicom+json'}\n",
    "\n",
    "response = client.get(url, headers=headers) #, verify=False)\n",
    "print(response)\n",
    "response.json()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Retrieve all instances within a series\n",
    "\n",
    "This retrieves all instances within a single series.\n",
    "\n",
    "_Details:_\n",
    "* Path: ../studies/{study}/series/{series}\n",
    "* Method: GET\n",
    "* Headers:\n",
    "   * `Accept: multipart/related; type=\"application/dicom\"; transfer-syntax=*`\n",
    "\n",
    "This series has 2 instances (green-square and red-triangle), so the response should return both instances. Validate that the response has a status code of OK and that both instances are returned.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "url = f'{base_url}/studies/{study_uid}/series/{series_uid}'\n",
    "headers = {'Accept':'multipart/related; type=\"application/dicom\"; transfer-syntax=*'}\n",
    "\n",
    "response = client.get(url, headers=headers) #, verify=False)\n",
    "response\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Retrieve metadata of all instances in series\n",
    "\n",
    "This request retrieves the metadata for all instances within a single series.\n",
    "\n",
    "_Details:_\n",
    "* Path: ../studies/{study}/series/{series}/metadata\n",
    "* Method: GET\n",
    "* Headers:\n",
    "   * `Accept: application/dicom+json`\n",
    "\n",
    "This series has 2 instances (green-square and red-triangle), so the response should return metatdata for both instances. Validate that the response has a status code of OK and that both instances metadata are returned.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "url = f'{base_url}/studies/{study_uid}/series/{series_uid}/metadata'\n",
    "headers = {'Accept':'application/dicom+json'}\n",
    "\n",
    "response = client.get(url, headers=headers) #, verify=False)\n",
    "print(response)\n",
    "response.json()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Retrieve a single instance within a series of a study\n",
    "\n",
    "This request retrieves a single instance.\n",
    "\n",
    "_Details:_\n",
    "* Path: ../studies/{study}/series{series}/instances/{instance}\n",
    "* Method: GET\n",
    "* Headers:\n",
    "   * `Accept: application/dicom; transfer-syntax=*`\n",
    "\n",
    "This should only return the instance red-triangle. Validate that the response has a status code of OK and that the instance is returned."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "url = f'{base_url}/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}'\n",
    "headers = {'Accept':'application/dicom; transfer-syntax=*'}\n",
    "\n",
    "response = client.get(url, headers=headers) #, verify=False)\n",
    "response\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Retrieve metadata of a single instance within a series of a study\n",
    "\n",
    "This request retrieves the metadata for a single instances within a single study and series.\n",
    "\n",
    "_Details:_\n",
    "* Path: ../studies/{study}/series{series}/instances/{instance}\n",
    "* Method: GET\n",
    "* Headers:\n",
    "   * `Accept: application/dicom; transfer-syntax=*`\n",
    "\n",
    "This should only return the metatdata for the instance red-triangle. Validate that the response has a status code of OK and that the metadata is returned.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "url = f'{base_url}/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}/metadata'\n",
    "headers = {'Accept':'application/dicom+json'}\n",
    "\n",
    "response = client.get(url, headers=headers) #, verify=False)\n",
    "print(response)\n",
    "response.json()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Retrieve one or more frames from a single instance\n",
    "\n",
    "This request retrieves one or more frames from a single instance.\n",
    "\n",
    "_Details:_\n",
    "* Path: ../studies/{study}/series{series}/instances/{instance}/frames/1,2,3\n",
    "* Method: GET\n",
    "* Headers:\n",
    "   * `Accept: multipart/related; type=\"application/octet-stream\"; transfer-syntax=1.2.840.10008.1.2.1` (Default) or\n",
    "   * `Accept: multipart/related; type=\"application/octet-stream\"; transfer-syntax=*` or\n",
    "   * `Accept: multipart/related; type=\"application/octet-stream\";`\n",
    "\n",
    "This should return the only frame from the red-triangle. Validate that the response has a status code of OK and that the frame is returned."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "url = f'{base_url}/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}/frames/1'\n",
    "headers = {'Accept':'multipart/related; type=\"application/octet-stream\"; transfer-syntax=*'}\n",
    "\n",
    "response = client.get(url, headers=headers) #, verify=False)\n",
    "response\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Query DICOM (QIDO)\n",
    "\n",
    "In the following examples, we search for items using their unique identifiers. You can also search for other attributes, such as PatientName and the like.\n",
    "\n",
    "> NOTE: Please see the [Conformance Statement](../resources/conformance-statement.md#supported-search-parameters) file for supported DICOM attributes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Search for studies\n",
    "\n",
    "This request searches for one or more studies by DICOM attributes.\n",
    "\n",
    "_Details:_\n",
    "* Path: ../studies?StudyInstanceUID={study}\n",
    "* Method: GET\n",
    "* Headers:\n",
    "   * `Accept: application/dicom+json`\n",
    "\n",
    "Validate that response includes 1 study and that response code is OK."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "url = f'{base_url}/studies'\n",
    "headers = {'Accept':'application/dicom+json'}\n",
    "params = {'StudyInstanceUID':study_uid}\n",
    "\n",
    "response = client.get(url, headers=headers, params=params) #, verify=False)\n",
    "response"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Search for series\n",
    "\n",
    "This request searches for one or more series by DICOM attributes.\n",
    "\n",
    "_Details:_\n",
    "* Path: ../series?SeriesInstanceUID={series}\n",
    "* Method: GET\n",
    "* Headers:\n",
    "   * `Accept: application/dicom+json`\n",
    "\n",
    "Validate that response includes 1 series and that response code is OK."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "url = f'{base_url}/series'\n",
    "headers = {'Accept':'application/dicom+json'}\n",
    "params = {'SeriesInstanceUID':series_uid}\n",
    "\n",
    "response = client.get(url, headers=headers, params=params) #, verify=False)\n",
    "response"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Search for series within a study\n",
    "\n",
    "This request searches for one or more series within a single study by DICOM attributes.\n",
    "\n",
    "_Details:_\n",
    "* Path: ../studies/{study}/series?SeriesInstanceUID={series}\n",
    "* Method: GET\n",
    "* Headers:\n",
    "   * `Accept: application/dicom+json`\n",
    "\n",
    "Validate that response includes 1 series and that response code is OK.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "url = f'{base_url}/studies/{study_uid}/series'\n",
    "headers = {'Accept':'application/dicom+json'}\n",
    "params = {'SeriesInstanceUID':series_uid}\n",
    "\n",
    "response = client.get(url, headers=headers, params=params) #, verify=False)\n",
    "response"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Search for instances\n",
    "\n",
    "This request searches for one or more instances by DICOM attributes.\n",
    "\n",
    "_Details:_\n",
    "* Path: ../instances?SOPInstanceUID={instance}\n",
    "* Method: GET\n",
    "* Headers:\n",
    "   * `Accept: application/dicom+json`\n",
    "\n",
    "Validate that response includes 1 instance and that response code is OK."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "url = f'{base_url}/instances'\n",
    "headers = {'Accept':'application/dicom+json'}\n",
    "params = {'SOPInstanceUID':instance_uid}\n",
    "\n",
    "response = client.get(url, headers=headers, params=params) #, verify=False)\n",
    "response"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Search for instances within a study\n",
    "\n",
    "This request searches for one or more instances within a single study by DICOM attributes.\n",
    "\n",
    "_Details:_\n",
    "* Path: ../studies/{study}/instances?SOPInstanceUID={instance}\n",
    "* Method: GET\n",
    "* Headers:\n",
    "   * `Accept: application/dicom+json`\n",
    "\n",
    "Validate that response includes 1 instance and that response code is OK.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "url = f'{base_url}/studies/{study_uid}/instances'\n",
    "headers = {'Accept':'application/dicom+json'}\n",
    "params = {'SOPInstanceUID':instance_uid}\n",
    "\n",
    "response = client.get(url, headers=headers, params=params) #, verify=False)\n",
    "response"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Search for instances within a study and series\n",
    "\n",
    "This request searches for one or more instances within a single study and single series by DICOM attributes.\n",
    "\n",
    "_Details:_\n",
    "* Path: ../studies/{study}/series/{series}/instances?SOPInstanceUID={instance}\n",
    "* Method: GET\n",
    "* Headers:\n",
    "   * `Accept: application/dicom+json`\n",
    "\n",
    "Validate that response includes 1 instance and that response code is OK.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "url = f'{base_url}/studies/{study_uid}/series/{series_uid}/instances'\n",
    "headers = {'Accept':'application/dicom+json'}\n",
    "params = {'SOPInstanceUID':instance_uid}\n",
    "\n",
    "response = client.get(url, headers=headers, params=params) #, verify=False)\n",
    "response"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Delete DICOM\n",
    "\n",
    "> NOTE: Delete is not part of the DICOM standard, but has been added for convenience.\n",
    "\n",
    "A 204 response code is returned when the deletion is successful. A 404 response code is returned if the item(s) have never existed or have already been deleted. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Delete a specific instance within a study and series\n",
    "\n",
    "This request deletes a single instance within a single study and single series.\n",
    "\n",
    "_Details:_\n",
    "* Path: ../studies/{study}/series/{series}/instances/{instance}\n",
    "* Method: DELETE\n",
    "* Headers: No special headers needed\n",
    "\n",
    "This deletes the red-triangle instance from the server. If it is successful the response status code contains no content."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#headers = {'Accept':'anything/at+all'}\n",
    "url = f'{base_url}/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}'\n",
    "response = client.delete(url) \n",
    "response"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Delete a specific series within a study\n",
    "\n",
    "This request deletes a single series (and all child instances) within a single study.\n",
    "\n",
    "_Details:_\n",
    "* Path: ../studies/{study}/series/{series}\n",
    "* Method: DELETE\n",
    "* Headers: No special headers needed\n",
    "\n",
    "\n",
    "This deletes the green-square instance (it is the only element left in the series) from the server. If it is successful the response status code contains no content."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#headers = {'Accept':'anything/at+all'}\n",
    "url = f'{base_url}/studies/{study_uid}/series/{series_uid}'\n",
    "response = client.delete(url) \n",
    "response"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Delete a specific study\n",
    "\n",
    "This request deletes a single study (and all child series and instances).\n",
    "\n",
    "_Details:_\n",
    "* Path: ../studies/{study}\n",
    "* Method: DELETE\n",
    "* Headers: No special headers needed\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#headers = {'Accept':'anything/at+all'}\n",
    "url = f'{base_url}/studies/{study_uid}'\n",
    "response = client.delete(url) \n",
    "response"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.7"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
